Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds .vue functionality #77

Merged
merged 11 commits into from
Jan 9, 2018
Merged

Conversation

prograhammer
Copy link
Contributor

@prograhammer prograhammer commented Nov 29, 2017

Hi guys,

This PR adds ability to work with .vue files (see #70).

For a quick test, I created a vue branch (out of the feature/vue branch) with the built lib folder included. So you can install like this:

npm install git://github.com/prograhammer/fork-ts-checker-webpack-plugin.git#vue --save-dev

  1. Turn on the vue option in the plugin in your webpack config:
    new ForkTsCheckerWebpackPlugin({
      tslint: true,
      vue: true
    })
  1. For linting to work in .vue files, you need to ensure your script tag's language attribute is set
    to ts or tsx (also make sure you include the .vue extension in all your import statements as shown below):
<script lang="ts">
import Hello from '@/components/hello.vue'

// ...

</script>
  1. If you are testing in Webpack, (in addition to this plugin) you'll need something like this in your rules:
{
  test: /\.ts$/,
  loader: 'ts-loader',
  include: [resolve('src'), resolve('test')],
  options: {
    appendTsSuffixTo: [/\.vue$/],
    transpileOnly: true
  }
},
{
  test: /\.vue$/,
  loader: 'vue-loader',
  options: vueLoaderConfig
},
  1. I also currently use tslint-config-standard so my tslint.json looks something like:
{
    "defaultSeverity": "error",
    "extends": [
      "tslint-config-standard"
    ]
}
  1. Ensure your tsconfig.json doesn't exclude .vue files:
// tsconfig.json
{
  "include": [
    "src/**/*.ts",
    "src/**/*.vue"
  ],
  "exclude": [
      "node_modules"
  ],
  "compilerOptions": {
    
    // ...

    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  }
}

If you are working in VSCode, you'll need extensions Vetur and TSLint Vue (a forked extension which I also currently maintain) and the editor will match the output you get from this fork-ts-checker-webpack-plugin.

Let me know what more you need or if you have any suggestions on code improvement/cleanup/etc.

Thanks!

@johnnyreilly
Copy link
Member

First of all: good work! What I say should be taken with a pinch of salt as I'm not a maintainer of this plugin.

That aside, I notice that with the current approach the code moves to always bring Vue into the mix. I'd be tempted to refactor the code to only use the Vue approach when you are definitely using the Vue functionality. Essentially treating the Vue functionality as something that can be feature flagged on or off. Do you understand what I mean? (I may not be being super clear)

@prograhammer
Copy link
Contributor Author

prograhammer commented Nov 29, 2017

@johnnyreilly Yeah I was thinking about that too. Do you think a flag to turn it on via the plugin options would be the way to go?

@johnnyreilly
Copy link
Member

I think that might be an idea (unless it can be reliably inferred somehow - possibly from the presence of 'vue' in compiler.options.resolve.extensions?)

* Assumes wildcard "@" is [project root]/src.
*/
static resolveNonTsModuleName(moduleName: string, containingFile: string, programConfigFile: string): string {
if (moduleName.indexOf('@/') === 0) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of hardcoded values, both webpack configuration (resolve.alias, resolve.modules) and tsconfig parameters (baseUrl and paths option) could be exploited here.

@@ -9,6 +9,7 @@ import WorkSet = require('./WorkSet');
import NormalizedMessage = require('./NormalizedMessage');
import CancellationToken = require('./CancellationToken');
import minimatch = require('minimatch');
import vueParser = require('vue-parser');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to use vue-template-compiler parseComponent function. Return type is SFCDescriptor, see flow typings https://github.com/vuejs/vue/blob/dev/flow/compiler.js#L172-L177

@Toilal
Copy link

Toilal commented Nov 30, 2017

I don't think we can rely on compiler.options.resolve.extensions because most ts-loader users enable appendTsSuffixTo to make *.vue files looks like *.ts files. (https://github.com/TypeStrong/ts-loader#appendtsxsuffixto-regexp-default)

@johnnyreilly
Copy link
Member

Fair point - maybe an explicit flag is better

@Toilal
Copy link

Toilal commented Nov 30, 2017

Maybe we can check vue-loader is available by trying to simply trying to require it ...

@prograhammer
Copy link
Contributor Author

prograhammer commented Nov 30, 2017

@Toilal That's a pretty clever idea (smart detection).

How do you guys feel about me moving all of this code into a separate module called CreateVueProgram.ts. I'll restore the code back to the way it was before the PR and just use a condition to decide whether to call CreateVueProgram here: https://github.com/Realytics/fork-ts-checker-webpack-plugin/blob/master/src/IncrementalChecker.ts#L161

There's no reason to be super DRY here. We can let Vue be its own thing and go it's own path by taking care of it in it's own module. Leave the normal program as it was in IncrementalChecker before I touched it. Then I'll start applying all those suggestions you made throughout the code @Toilal.

Thoughts?
Cc @johnnyreilly

@johnnyreilly
Copy link
Member

Sounds promising!

@prograhammer
Copy link
Contributor Author

prograhammer commented Dec 1, 2017

Alright guys,

Everything works great on my example project on my local machine because the project is a Vue project with vue-loader in it's node_modules. You can see I added the condition here. But Travis fails because I imagine there's no vue-loader in the project it is testing with. This is a nice approach, but what should we do about Travis? Or should we do something else instead, like check for an option from the plugin?

Update: Nevermind! facepalm Thanks @Toilal

const projectPath = path.dirname(this.programConfigFile);
const contextResolution = Resolve.sync('vue-loader', { basedir: projectPath });

return require(contextResolution) ? true : false;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems you still have to try/catch this line and return false on error ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right! Brain freeze! Of course that's the problem. LOL. Will fix...

@prograhammer
Copy link
Contributor Author

prograhammer commented Dec 1, 2017

voila! @Toilal !

All checks passing now. I'll merge master into it (and fix any conflicts) after all feedback is in. Now regarding feedback, I looked over webpack resolve and so far I don't really see any API documentation around that for using it programmatically. Also, for vue-template-compiler there are no types defined. We just need a fast AST tree parser and that's what vue-parser is doing here (uses parse5 under the hood).

@prograhammer
Copy link
Contributor Author

prograhammer commented Dec 1, 2017

Alright guys, there's a huge line of eager people waiting for this (there's over 1,300 people who have TSLint Vue currently installed in their VSCode editor and those Vue folks are almost all guaranteed to be on Webpack). And there's no other option availabe for Vue developers that runs at the typechecker level. Since I've split off the code so it doesn't disrupt the original program, what can we do to fast-track this PR? I'll continue to help polish things up afterwards as well.

@johnnyreilly
Copy link
Member

@prograhammer the person who needs to feed back is @piotr-oles. This is his project and only he can merge I'm afraid. In the short term you could consider publishing the fork to a GitHub repo with the compiled dist included as well. We did that whilst we were waiting for 0.2.9 to be published. See here: https://github.com/TypeStrong/fork-ts-checker-webpack-plugin

Speaking of which, I must take that down now 0.2.9 has shipped.

@johnnyreilly
Copy link
Member

Background to how that came to pass here: #64

@prograhammer
Copy link
Contributor Author

Ah, gotcha. No worries!

Yeah I've got this branch here that I'll keep promoting to folks in the meantime: npm install git://github.com/prograhammer/fork-ts-checker-webpack-plugin.git#vue --save-dev

@johnnyreilly
Copy link
Member

Sweet!

}

nextIteration() {
if (!this.watcher) {
this.watcher = new FilesWatcher(this.watchPaths, ['.ts', '.tsx']);
this.watcher = new FilesWatcher(this.watchPaths, ['.ts', '.tsx', '.vue']);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the .vue be conditional based on whether vue is in the mix or not please?

nextIteration() {
if (!this.watcher) {
this.watcher = new FilesWatcher(this.watchPaths, ['.ts', '.tsx']);
this.watcher = new FilesWatcher(this.watchPaths, ['.ts', '.tsx', '.vue']);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could including vue be conditional upon whether this is a vue program or not please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it hurt to always include .vue? It doesn't really bother the folks not on Vue.js. Otherwise it's another condition we'll need to make here for the watcher. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 things really:

  1. It will make a marginal speed difference for non-Vue users. I'm philosophically opposed to that 😄
  2. It bothers me on an OCD level that Vue files would be always in the mix when they needn't be. This is more subjective I grant you but makes me twitch in unhappy ways. It is my suffering.

Copy link
Contributor Author

@prograhammer prograhammer Dec 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's always a battle between code readability and micro-optimization (in this case the optimization may be significant). OCD kicks in for both 😄
But let me see of a clean way to condition that...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks man!

@@ -158,7 +176,14 @@ class IncrementalChecker {
}
}

this.program = IncrementalChecker.createProgram(this.programConfig, this.files, this.watcher, this.program);
if (this.hasVueLoader()) {
this.programConfig = this.programConfig || VueProgram.loadProgramConfig(this.programConfigFile);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this branch isn't it always the VueProgram you use?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No no, this condition is here to detect if vue-loader is found in the user's project and then use the VueProgram if it is. We don't need to maintain a separate branch for Vue if we have this condition. It can be merged into master.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.programConfig || VueProgram.loadProgramConfig(this.programConfigFile) but wouldn't this statement always use programConfig? Or have I missed something?

Copy link
Contributor Author

@prograhammer prograhammer Dec 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first pass over that line this.programConfig will be undefined so it will grab the return from the right side of the condition (VueProgram.loadProgramConfig(...). The next time it passes over that line this.programConfig will have a value and it will grab that instead of performing another unnecessary VueProgram.loadProgramConfig(....

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool - WFM!

@johnnyreilly
Copy link
Member

It's looking good @prograhammer - I've a few comments as you can see but it's basically good. I'm glad that this is mostly triggered only when Vue is in the mix.

@piotr-oles would you be able to take a look at this when you get a mo?

@jthomaschewski
Copy link

jthomaschewski commented Dec 4, 2017

Thanks for the effort @prograhammer
I tried using your fork in a project of mine but I have problems with mixed JS/TS vue components.

When importing a JSModule.vue module with <script> from a TSModule.vue module with <script lang="ts"> (or any .ts file) I get:

ERROR in /app/TSModule.vue(2,27):
TS2306: File '/app/JSModule.vue' is not a module.

Same result regardless of allowJs setting in tsconfig.json.
No issues when running tsc -p . --noEmit or ts-loader withtranspileOnly: false

Importing vue components with TS works fine from both plain .ts and ts-enabled .vue files

EDIT:
Importing such JS vue modules does work when using absolute paths, e.g @/components/JSModule.vue. But I would expect this to also work with relative paths as I have the following declaration file for Vue:

declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}

@CKGrafico
Copy link
Contributor

Please accept that!

@prograhammer
Copy link
Contributor Author

prograhammer commented Dec 5, 2017

@JBBr A couple things to point out here:

  1. You don't need that wildcard declaration (declare module '*.vue' ...). That defeats the purpose of this PR and TypeScript. You won't get full type information from your Vue modules if you do that.
  2. You need to ensure your scripts have lang="ts" or lang="tsx" for them to be parsed. I just updated the code to allow js and jsx as well, but that doesn't seem like your problem.
  3. The code works for relative paths too. Maybe something else is going on. The best way to know is to give us an example project repo on Github we can pull down and see the problem.

CKGrafico added a commit to pwa-builder/PWABuilder that referenced this pull request Dec 5, 2017
@prograhammer
Copy link
Contributor Author

prograhammer commented Dec 5, 2017

@johnnyreilly All your feedback is all done brother, 😸

@prograhammer
Copy link
Contributor Author

prograhammer commented Jan 1, 2018

Hi @Punit-wingify

I can confirm in my own project that I am able to access this.$store.state.some.foo and it works perfectly with this PR. Make sure you have the latest Vue and Vuex. You can see here that when you add Vuex to your project the Vue type interface is extended to include $store: Store<any>.

@Punit-wingify
Copy link

@prograhammer Okay thanks, just needed the confirmation 👍

@TheLarkInn
Copy link

Anything we [at webpack] can help with furthering this along. This is super exciting and we've heard a few downstream requests come our way in regards to its status.

Happy to help in any way possible or unblock any blockers if they exist.

@johnnyreilly
Copy link
Member

johnnyreilly commented Jan 6, 2018

Thanks @TheLarkInn - that's always appreciated!

I think the current status is that @prograhammer has fixed all the issues we're aware of and the only thing we're waiting on is @piotr-oles to confirm he's happy to merge. Exciting!

@piotr-oles are we ready to go now?

@keenwon
Copy link

keenwon commented Jan 8, 2018

@prograhammer

After turn on the vue option, I got a error:

new ForkTsCheckerWebpackPlugin({
  vue: true
})
{
  test: /\.ts$/,
  exclude: /node_modules|vue\/src/,
  loader: 'ts-loader',
  options: {
    transpileOnly: true,
    appendTsSuffixTo: [/\.vue$/]
  }
},
{
  test: /\.vue$/,
  loader: 'vue-loader',
  options: {
    esModule: true,
    loaders: {
      ts: 'ts-loader',
      scss: 'vue-style-loader!css-loader!sass-loader'
    }
  }
}
Starting type checking service...
Using 1 worker with 2048MB memory limit
webpack building...
D:\Code\nei7\node_modules\fork-ts-checker-webpack-plugin\lib\service.js:22
        throw error;
        ^

TypeError: Cannot read property 'flags' of undefined
    at getBaseTypes (D:\Code\nei7\node_modules\typescript\lib\typescript.js:28172:37)
    at getSuperClass (D:\Code\nei7\node_modules\typescript\lib\typescript.js:37548:21)
    at isPropertyDeclaredInAncestorClass (D:\Code\nei7\node_modules\typescript\lib\typescript.js:37537:29)
    at checkPropertyNotUsedBeforeDeclaration (D:\Code\nei7\node_modules\typescript\lib\typescript.js:37507:21)
    at checkPropertyAccessExpressionOrQualifiedName (D:\Code\nei7\node_modules\typescript\lib\typescript.js:37477:13)
    at checkPropertyAccessExpression (D:\Code\nei7\node_modules\typescript\lib\typescript.js:37448:20)
    at checkExpressionWorker (D:\Code\nei7\node_modules\typescript\lib\typescript.js:40582:28)
    at checkExpression (D:\Code\nei7\node_modules\typescript\lib\typescript.js:40529:42)
    at checkExpressionCached (D:\Code\nei7\node_modules\typescript\lib\typescript.js:40406:38)
    at checkVariableLikeDeclaration (D:\Code\nei7\node_modules\typescript\lib\typescript.js:42623:43)

@prograhammer
Copy link
Contributor Author

prograhammer commented Jan 8, 2018

Hi @keenwon

  1. Instead of passing Vue files to ts-loader from vue-loader, try updating your vue-loader options to look like this:
    loaders: {
      // ts: 'ts-loader',  <-- remove this
      scss: 'vue-style-loader!css-loader!sass-loader'
    }

  1. And then you would need to update your ts-loader exclude statement (try an include statement as shown in the first post).

  2. Are using this build: npm install git://github.com/prograhammer/fork-ts-checker-webpack-plugin.git#vue --save-dev?

  3. Create a simplified example project (strip everything down, remove your biz logic or whatever) on your Github and give us a link to it here so we can diagnose the error first hand with what you are doing in the project.

@piotr-oles piotr-oles changed the base branch from next to master January 9, 2018 19:43
@piotr-oles piotr-oles merged commit 48055c6 into TypeStrong:master Jan 9, 2018
@piotr-oles
Copy link
Collaborator

Looks good, will be released as 0.3.0 :) Great work!

@keenwon
Copy link

keenwon commented Jan 10, 2018

@prograhammer this project can reproduce the problem.
https://github.com/keenwon/fork-ts-checker-webpack-plugin-test

@prograhammer
Copy link
Contributor Author

prograhammer commented Jan 10, 2018

@keenwon

It looks like you found a strange TypeScript bug which is not coming from fork-ts-checker-webpack-plugin (previous version or latest 0.3.0 with Vue functionality) but rather TypeScript itself (even the latest TypeScript version). The reason you are seeing it when you turn on the vue flag is not due to fork-ts-checker-webpack-plugin but is because of that specific type of error inside the class (with the experimental decorator) in that Vue file...

For example, take this class with the error Block-scoped variable 'bar' used before its declaration:

import Vue from 'vue'
import { Component, Prop, Watch } from 'vue-property-decorator'

@Component
export default class Test extends Vue {
  public foo: string = this.bar  // <-- This is an error that TypeScript can handle. Decorator may be the problem.
  private bar: string
}

and put it in a regular .ts file such as test.ts and import it into your main entry. Remove fork-ts-checker-webpack-plugin completely from your webpack config and then update ts-loader (remove the transpileOnly) to compile. You'll see TypeScript still breaks on that code. It even breaks the normal (non-Vue) TSLint extension in VSCode.

I'll report the issue to TypeScript (as well as look into it myself) and put the link here for anyone to follow. In the mean time, just fix that error in that class. Let us know if anything else breaks, thanks!

@johnnyreilly
Copy link
Member

Perhaps a silly question, but why not just swap the foo and bar lines? It seems like a legitimate TypeScript error to me...

@prograhammer
Copy link
Contributor Author

prograhammer commented Jan 10, 2018

Yeah but it's an error TypeScript should be able to handle without breaking. It handles this error fine outside of experimental decorators (reports it as Block-scoped variable 'bar' used before its declaration). 😢 But all other errors seem to work fine in experimental decorators.

@johnnyreilly
Copy link
Member

Hmmm weird - definitely report it on the Typescript repo I reckon

@Toilal
Copy link

Toilal commented Jan 10, 2018

I tried the new release with vue: true and tslint: true, and it works really well.

Only issue I found i that linter is running on empty script tag content, even when a src attribute is defined.

<template src="./App.html"></template>
<style scoped src="./App.css"></style>
<script lang="ts" src="./App.ts"></script>

This raises tslint errors for missing EOL

ERROR in C:/devel/projects/vue-ts-test/src/App.vue
(3,91): file should end with a newline

@CKGrafico
Copy link
Contributor

CKGrafico commented Jan 10, 2018

@Toilal can you upload your working example? in my nuxt project linting is still not working on vue files :/
About EOL I think that is a rule that has sense 4-5 years ago but now I usually disable it.

In my case
Working
import SkipLink from './SkipLink.vue';
Not working
import SkipLink from '~/components/SkipLink.vue';

But if I disable this plugin, everything is working

@CKGrafico
Copy link
Contributor

Wow ok my error is here... @prograhammer https://github.com/Realytics/fork-ts-checker-webpack-plugin/pull/77/files#r159148429 haha '~' is super common to! (In Microsoft environments more) I'll made a PR for this :D

@CKGrafico CKGrafico mentioned this pull request Jan 10, 2018
@prograhammer
Copy link
Contributor Author

prograhammer commented Jan 10, 2018

@Toilal

That's weird since vue-parser adds an EOL here: https://github.com/prograhammer/vue-parser/blob/master/src/index.ts#L27

Maybe it's complaining about the Vue file itself. Can you add an EOL to it and see what happens:

<template src="./App.html"></template>
<style scoped src="./App.css"></style>
<script lang="ts" src="./App.ts"></script>
<-- EOL here

@prograhammer
Copy link
Contributor Author

@CKGrafico Of course you know what the best solution is right? Switch to Ubuntu Gnome 3 for development (and dual boot with Windows for gaming/photoshop/etc.). 😆 😆 😆

@Toilal
Copy link

Toilal commented Jan 11, 2018

I can't reproduce the EOL issue now ... Maybe after upgrading to tslint 5.9.1 ? (5.9.0 was broken at release, but shortly fixed)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.